using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
using SharpMud.Server.Systems;
using SharpMud.Server.App;
using SharpMud.MudLib.Common.Resourcing;
using SharpMud.MudLib.Common.Environment.Persistance;

namespace SharpMud.MudLib.Common.Environment
{
    /// <summary>
    /// A nice, generic environment management system that abstracts area loading and management enough
    /// that all mudlib administrators have to worry about is coding their own area format (both on disk
    /// and in memory).
    /// </summary>
    public class EnvironmentManager<TAreaInstance> : Server.Systems.PulsarServerSystem, IEnvironmentManager where TAreaInstance : IArea, new()
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);
        internal const string _UriNodeName = "Environment";

        #region Private Variables
        private IBaseMudLibRoot                         _Host;
        private EnvironmentConfiguration<TAreaInstance> _Configuration;
        protected List<TAreaInstance>                     _ActiveAreas;
        private PrivateResourcingHelper                 _ResourcingHelper;
        #endregion

        #region Constructors
        public EnvironmentManager(EnvironmentConfiguration<TAreaInstance> configuration)
        {
            this._Host = (IBaseMudLibRoot)base.Host;
            this._Configuration = configuration;
            _ActiveAreas = new List<TAreaInstance>();
        }
        #endregion

        public TAreaInstance[] GetActiveAreas()
        {
            TAreaInstance[] aa;
            lock(_ActiveAreas)
            {
                aa = _ActiveAreas.ToArray();
            }
            return aa;
        }

        protected override void Pulse()
        {
            lock (_ActiveAreas)
            {
                foreach (IArea area in _ActiveAreas)
                {
                    area.Pulse();
                }
            }
        }

        protected override void _Initialize()
        {
            this._Host = (IBaseMudLibRoot)base.Host;
            _ResourcingHelper = new PrivateResourcingHelper(new Uri(_Host.BaseUri, _UriNodeName));
        }

        protected override void _Start()
        {
            base._Start();

            //Load areas from configuration
            TAreaInstance[] loadedAreas = LoadAreasFromConfig();
            //Make sure the loaded areas are registered with the hierarchical resourcing system
            foreach (IArea area in loadedAreas)
            {
                if (area is IGuidIdentifiedResourceHost)
                    this._ResourcingHelper.AddResourceProxy((IGuidIdentifiedResourceHost)area);
                if (area is IUriIdentifiedResourceHost)
                    this._ResourcingHelper.AddResourceProxy((IUriIdentifiedResourceHost)area);
            }
            //Make the loaded areas active
            _ActiveAreas.AddRange(loadedAreas);
        }

        private TAreaInstance[] LoadAreasFromConfig()
        {
            List<TAreaInstance> loadedAreas = new List<TAreaInstance>();
            log.Info("Loading areas...");
            //Loop through each AreaLoaderMetadatum
            foreach (AreaLoaderMetadatum<TAreaInstance> alm in this._Configuration.AreaLoaders)
            {
                IAreaStreamer<TAreaInstance> areaStreamer;
                bool ex = false;
                //Check and see if we acquired the IAreaStreamer successfully
                try
                {
                    areaStreamer = alm.GetAreaStreamer();
                }
                catch (System.Exception e)
                {
                    log.Warn("Exception thrown while instanciating IAreaStreamer<" + typeof(TAreaInstance).FullName + "> (" + alm.AreaStreamer + ")",e);
                    areaStreamer = null;
                    ex = true;
                }
                if (!ex && areaStreamer == null)
                {
                    log.Error("Unable to instanciate IAreaStreamer<" + typeof(TAreaInstance).FullName + "> (" + alm.AreaStreamer + ")");
                }

                //Get all the files that the current AreaLoaderMetadatum applies to
                List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();
                files.AddRange(alm.GetApplicableFiles(RootEnvironmentFolder));

                System.Uri currentDirectoryUri = new Uri(System.Environment.CurrentDirectory);
                if (areaStreamer == null)
                {
                    //Since we didn't successfully load the IAreaStreamer that the AreaLoaderMetadatum specifies,
                    //we need to tell the sysop that these areas will not load.
                    System.Text.StringBuilder sb = new StringBuilder();
                    foreach (System.IO.FileInfo file in files)
                    {
                        //For each area we can't load, find out the relative path and declare it
                        //to the log
                        System.Uri currentFileUri = new Uri(file.FullName);
                        System.Uri relativeUri = currentFileUri.MakeRelativeUri(currentDirectoryUri);

                        log.Warn("[unable to process] " + relativeUri.ToString());
                    }
                    sb = null;
                }
                else
                {
                    foreach (System.IO.FileInfo file in files)
                    {
                        //For each area we can load, find out the relative path (because it is shorter)
                        //and log it
                        System.Uri currentFileUri = new Uri(file.FullName);
                        System.Uri relativeUri = currentFileUri.MakeRelativeUri(currentDirectoryUri);
                        
                        log.Info("[" + alm.AreaStreamer + "][processing...] " + relativeUri.ToString() );

                        //Open a stream to the area file
                        System.IO.Stream areaInputStream = file.OpenRead();
                        //Process the area stream
                        IAreaLoadResults<TAreaInstance> alr = areaStreamer.LoadArea(areaInputStream);
                        //Log each message resulting from processing the area stream
                        foreach (IAreaLoadMessage almsg in alr.Messages)
                        {
                            if (almsg.IsCritical)
                            {
                                log.Warn("\t[" + almsg.Row.ToString() + "," + almsg.Column.ToString() + "][!]: " + almsg.Message);
                            }
                            else
                            {
                                log.Warn("\t[" + almsg.Row.ToString() + "," + almsg.Column.ToString() + "][ ]: " + almsg.Message);
                            }
                        }
                        if (alr.LoadedArea != null)
                        {
                            log.Info("[" + alm.AreaStreamer + "][LOADED] " + relativeUri.ToString());
                            loadedAreas.Add(alr.LoadedArea);
                        }
                        else
                        {
                            log.Warn("[" + alm.AreaStreamer + "][NOT LOADED] " + relativeUri.ToString());
                        }
                    }
                }
            }
            log.Info("DONE loading areas");
            return loadedAreas.ToArray();
        }

        public virtual string RootEnvironmentFolder
        {
            get
            {
                return Application.GetSubfolderIntelligently(Application.GetSubfolderIntelligently(_Host.RootDataFolder, "environment"), "areas");
            }
        }

        #region IGuidIdentifiedResourceHost Members

        public bool IsDefined(Guid guid)
        {
            return _ResourcingHelper.IsDefined(guid);
        }

        public object GetResource(Guid guid)
        {
            return _ResourcingHelper.GetResource(guid);
        }

        #endregion

        #region IUriIdentifiedResourceHost Members

        public Uri BaseUri
        {
            get { return _ResourcingHelper.BaseUri; }
        }

        public bool IsDefined(Uri resourceIndicator)
        {
            return _ResourcingHelper.IsDefined(resourceIndicator);
        }

        public object GetResource(Uri resourceIndicator)
        {
            return _ResourcingHelper.GetResource(resourceIndicator);
        }

        #endregion

        #region IEnvironmentManager Members

        public ICollection<IArea> ActiveAreas
        {
            get { return (ICollection<IArea>)(_ActiveAreas); }
        }

        public IRoom[] GetActiveRooms()
        {
            List<IRoom> rooms = new List<IRoom>();
            foreach (IArea area in _ActiveAreas)
            {
                rooms.AddRange(area.GetRooms());
            }
            return rooms.ToArray();
        }

        #endregion
    }

    #region Temp
    //[XmlRoot(GenericInstanceSerialization)]
    //[XmlInclude(typeof(GenericInstanceXmlSerialization))]
    ////[XmlInclude(typeof(GenericInstanceBinarySerialization))]
    //public abstract class GenericInstanceSerialization
    //{
    //    private string          _InstanceType;

    //    protected GenericInstanceSerialization()
    //    {
    //        _InstanceType = null;
    //    }
    //    protected GenericInstanceSerialization(System.Type instanceType)
    //    {
    //        _InstanceType = instanceType.AssemblyQualifiedName;
    //    }

    //    [XmlAttribute("InstanceType")]
    //    public string InstanceType
    //    {
    //        get
    //        {
    //            return _InstanceType;
    //        }
    //        set
    //        {
    //            _InstanceType = value;
    //        }
    //    }

    //    [XmlIgnore()]
    //    public System.Type ActualInstanceType
    //    {
    //        get
    //        {
    //            return System.Type.GetType(_InstanceType, false, true);
    //        }
    //        set
    //        {
    //            _InstanceType = value.AssemblyQualifiedName;
    //        }
    //    }

    //    public abstract object AcquireDeserializedInstance();
    //}

    //[XmlRoot("GenericInstanceXmlSerialization")]
    //public class GenericInstanceXmlSerialization : GenericInstanceSerialization
    //{
    //    private List<string> _NeededTypes;

    //    public GenericInstanceXmlSerialization()
    //    {
    //    }
    //    public GenericInstanceXmlSerialization(System.Type instanceType)
    //        :base(instanceType)
    //    {
    //    }
    //    public GenericInstanceXmlSerialization(System.Type instanceType, params System.Type[] neededTypes)
    //        :base(instanceType)
    //    {
    //        ActualNeededTypes = neededTypes;
    //    }

    //    [XmlElement("NeededType")]
    //    public string[] NeededTypes
    //    {
    //        get
    //        {
    //            return _NeededTypes.ToArray();
    //        }
    //        set
    //        {
    //            _NeededTypes.AddRange(value);
    //        }
    //    }

    //    [XmlIgnore()]
    //    public System.Type[] ActualNeededTypes
    //    {
    //        get
    //        {
    //            List<Type> ts = new List<Type>();
    //            foreach (string s in _NeededTypes)
    //            {
    //                ts.Add(System.Type.GetType(s, false, true));
    //            }
    //            return ts.ToArray();
    //        }
    //        set
    //        {
    //            foreach (System.Type t in value)
    //            {
    //                _NeededTypes.Add(t.AssemblyQualifiedName);
    //            }
    //        }
    //    }

    //    public System.Xml.Serialization.XmlSerializer GenerateSerializer()
    //    {
    //        return new XmlSerializer(this.ActualInstanceType, this.ActualNeededTypes);
    //    }
    //}
    #endregion
}
